import { useForkRef } from '@/components';
import clsx from 'clsx';
import React, { forwardRef, useContext, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { findDOMNode } from 'react-dom';
import styles from './index.less';
import { ConfigCtx, Context, RowCtx } from './shared';

DraggableItem = forwardRef(DraggableItem);

function DraggableItem({ row, index, className, ...props }, outerRef) {
  const ref = useRef();
  const forkedRef = useForkRef(ref, outerRef);
  const ctx = useContext(Context);
  const rowCtx = useRef({ row, ref, index: null, currentIndex: null }).current;
  const { itemComponent: Comp = 'div', itemClassName, itemDraggingClassName } = useContext(ConfigCtx);

  const [dragging, setDragging] = useState(false);

  const setStyle = (prop, value) => {
    const element = findDOMNode(ref.current);
    if (element) element.style[prop] = value;
  };

  useEffect(() => {
    rowCtx.row = row;
  }, [row]);

  useEffect(() => {
    rowCtx.renderIndex = index;
  }, [index]);

  useLayoutEffect(() => {
    ctx.mountRow(rowCtx);
    return () => {
      ctx.unmountRow(rowCtx);
    };
  }, []);

  useEffect(() => {
    return ctx.subscribe((draggingRowCtx) => {
      setDragging(draggingRowCtx === rowCtx);
    });
  }, []);

  useEffect(() => {
    return ctx.subOrder(([currentRowCtx, currentIndex, ctxList]) => {
      if (currentRowCtx === rowCtx || !ctxList.includes(rowCtx)) return;
      const { index } = rowCtx;
      let trans = null;
      if (currentRowCtx && currentIndex !== -1 && index != null) {
        // drag to front of this.
        if (currentRowCtx.index > index && currentIndex <= index)
          trans = `translate(0px, ${currentRowCtx.rowHeight}px)`;

        // drag to back of this.
        if (currentRowCtx.index < index && currentIndex >= index)
          trans = `translate(0px, -${currentRowCtx.rowHeight}px)`;
      }
      setStyle('transform', trans);
    });
  }, []);

  useLayoutEffect(() => {
    if (dragging) {
      setStyle('transition', 'none');
      return ctx.subTransform((transform) => {
        if (transform) setStyle('transform', `translate3d(${transform.x}px, ${transform.y}px, 1px)`);
      });
    } else {
      const element = findDOMNode(ref.current);
      if (!element) return;
      setStyle('transition', 'transform .2s ease-out');
      setStyle('transform', 'translate3d(0, 0, 1px)');
      const onTransitionEnd = () => {
        setStyle('transition', null);
        setStyle('transform', null);
        element.removeEventListener('transitionend', onTransitionEnd);
      };
      element.addEventListener('transitionend', onTransitionEnd);
      setTimeout(() => {
        if (typeof index === 'number' && rowCtx.currentIndex === index) {
          setStyle('transition', null);
          setStyle('transform', null);
          element.removeEventListener('transitionend', onTransitionEnd);
        }
      });
    }
  }, [dragging]);

  const klassName = clsx(
    className,
    styles.movableRow,
    itemClassName,
    dragging && [styles.moving, itemDraggingClassName],
  );

  return (
    <RowCtx.Provider value={rowCtx}>
      <Comp ref={forkedRef} className={klassName} {...props} />
    </RowCtx.Provider>
  );
}

export default DraggableItem;
